﻿using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
using System.Collections;
using UnityEngine.ResourceManagement.ResourceLocations;

namespace Elvex
{
    public class AssetRequest
    {
        public event System.Action<AssetRequest> completed;

        public AssetRequest()
        {

        }

        public Object Asset { get; internal set; }

        internal void OnCompleted()
        {
            completed.Invoke(this);
        }
    }

    public class AssetRequest<T> where T : Object
    {
        public event System.Action<AssetRequest<T>> completed;

        public T Asset { get; internal set; }

        internal void OnCompleted()
        {
            completed.Invoke(this);
        }
    }

    public class AssetsRequest
    {
        public event System.Action<AssetsRequest> completed;
        public event System.Action<AssetsRequest, Object> assetCompleted;

        public AssetsRequest()
        {

        }

        internal int AssetsCount { get; set; } = 0;

        public List<Object> Assets { get; } = new List<Object>();

        internal void OnCompleted()
        {
            completed.Invoke(this);
        }

        internal void OnAssetCompleted(Object asset)
        {
            var req = this;
            Assets.Add(asset);
            assetCompleted.Invoke(req, asset);
        }
    }

    public class AssetsRequest<T> where T : Object
    {
        public event System.Action<AssetsRequest<T>> completed;
        public event System.Action<AssetsRequest<T>, T> assetCompleted;

        public AssetsRequest()
        {

        }

        internal int AssetsCount { get; set; } = 0;

        public List<T> Assets { get; } = new List<T>();

        internal void OnCompleted()
        {
            completed.Invoke(this);
        }

        internal void OnAssetCompleted(T asset)
        {
            Assets.Add(asset);
            assetCompleted.Invoke(this, asset);
        }
    }

    // 有序资源加载请求
    public class AssetsTableRequest<T> : AsyncRequest where T : Object
    {
        public event System.Action<AssetsTableRequest<T>, string, T> assetCompleted;

        public int AssetsCount { get; set; } = 0;

        public int CompletedAssetsCount { get; set; } = 0;

        internal AssetsTableRequest(int assetscount)
        {
            AssetsCount = assetscount;
        }

        internal Dictionary<string, T> Assets { get; } = new Dictionary<string, T>();

        internal void OnAssetCompleted(string path, T asset)
        {
            ++CompletedAssetsCount;
            Assets.Add(path, asset);
            assetCompleted.Invoke(this, path, asset);
            if ( Assets.Count >= AssetsCount )
            {
                SendCompleteMessage();
            }
        }
    }

    public static class Assets
    {
        enum AssetType
        {
            None,
            Resourcs, // res资源目录下
            Assets, // 资源目录下
        }

        public static void SetMode()
        {
            //Addressables.MergeMode
        }

#if UNITY_EDITOR
        public static bool IsDebug { get; set; } = false;
#endif

        const string AssetPathTags = "Assets/";
        
        const string ResourcePathTags = "Resources/";

        private static AssetType PathToAssetType(string path)
        {
            if (string.IsNullOrEmpty(path))
            {
                Debug.LogErrorFormat("AssetPath({0}) is invalid.", null == path ? "" : path);
                return AssetType.None;
            }
            else if (path.StartsWith(AssetPathTags))
            {
                return AssetType.Assets;
            }
            else if (path.Contains(ResourcePathTags))
            {
                return AssetType.Resourcs;
            }
            else
            {
                Debug.LogErrorFormat("AssetPath({0}) is invalid.", null == path ? "" : path);
                return AssetType.None;
            }
        }

        public static Object Load(string path)
        {
            var assettype = PathToAssetType(path);
            Object obj = null;
            switch (assettype)
            {
                case AssetType.Resourcs:
                    obj = LoadResource(path);
                    break;
                case AssetType.Assets:
                    obj = LoadAsset(path);
                    break;
                default:
                    break;
            }

            if (null == obj)
            {
                Debug.LogErrorFormat("Asset({0}) Load Failed.", path);
            }

            return obj;
        }

        public static T Load<T>(string path) where T : Object
        {
            var assettype = PathToAssetType(path);
            T obj = null;
            switch (assettype)
            {
                case AssetType.Resourcs:
                    obj = LoadResource<T>(path);
                    break;
                case AssetType.Assets:
                    obj = LoadAsset<T>(path);
                    break;
                default:
                    break;
            }

            if (null == obj)
            {
                Debug.LogErrorFormat("Asset({0}) Load Failed.", path);
            }
            return obj;
        }

        public static AssetRequest LoadAsync(string path)
        {
            var assettype = PathToAssetType(path);
            switch (assettype)
            {
                case AssetType.Resourcs:
                    return LoadResourceAsync(path);
                case AssetType.Assets:
                    return LoadAssetAsync(path);
                default:
                    return null;
            }
        }

        public static AssetRequest<T> LoadAsync<T>(string path) where T : Object
        {
            var assettype = PathToAssetType(path);
            switch (assettype)
            {
                case AssetType.Resourcs:
                    return LoadResourceAsync<T>(path);
                case AssetType.Assets:
                    return LoadAssetAsync<T>(path);
                default:
                    return null;
            }
        }

        public static T Load<T>(string path, string name) where T : Object
        {
            T[] objs = LoadAll<T>(path);
            foreach (var item in objs)
            {
                if (item.name == name)
                {
                    return item;
                }
            }
            Debug.LogWarningFormat("{1} does not Exist in {0}.", path, name);
            return null;
        }

        public static T[] LoadAll<T>(string path) where T : Object
        {
            return null;
            //return LoadAll<T>(path, AssetPathToAssetType(path));
        }

        public static Object[] Load(List<string> paths)
        {
            List<string> resourcepaths = new List<string>();
            List<string> assetpaths = new List<string>();
            foreach (var path in paths)
            {
                var assettype = PathToAssetType(path);
                

                assetpaths.Add(path);
            }

            return null;
        }

        public static T[] Load<T>(List<string> paths) where T : Object
        {
            return null;
        }

        public static AssetsRequest LoadAsync(List<string> paths)
        {
            var req = new AssetsRequest()
            {
                AssetsCount = paths.Count
            };

            List<string> resourcepaths = new List<string>();
            List<string> assetpaths = new List<string>();
            foreach (var path in paths)
            {
                switch (PathToAssetType(path))
                {
                    case AssetType.Resourcs:
                        {
                            resourcepaths.Add(path);
                        }
                        break;
                    case AssetType.Assets:
                        {
                            assetpaths.Add(path);
                        }
                        break;
                    default:
                        return null;
                }
            }

            LoadAssetsAsync(assetpaths).assetCompleted += (op, asset) => {
                req.OnAssetCompleted(asset);
            };

            LoadResourcesAsync(resourcepaths).assetCompleted += (op, asset) => {
                req.OnAssetCompleted(asset);
            };
            return req;
        }

        public static AssetsRequest<T> LoadAsync<T>(List<string> paths) where T : Object
        {
            var req = new AssetsRequest<T>()
            {
                AssetsCount = paths.Count
            };

            List<string> resourcepaths = new List<string>();
            List<string> assetpaths = new List<string>();
            foreach (var path in paths)
            {
                switch (PathToAssetType(path))
                {
                    case AssetType.Resourcs:
                        {
                            resourcepaths.Add(path);
                        }
                        break;
                    case AssetType.Assets:
                        {
                            assetpaths.Add(path);
                        }
                        break;
                    default:
                        //return null;
                        break;
                }
            }

            var resourcesreq = LoadResourcesAsync<T>(resourcepaths);
            resourcesreq.assetCompleted += (op, asset) => {
                req.OnAssetCompleted(asset);
            };

            resourcesreq.completed += (op) => {
                if(op.Assets.Count >= op.AssetsCount )
                {
                    req.OnCompleted();
                }
            };

            var assetsreq = LoadAssetsAsync<T>(assetpaths);
            assetsreq.assetCompleted += (op, asset)=> {
                req.OnAssetCompleted(asset);
            };

            assetsreq.completed += (op) => {
                if (op.Assets.Count >= op.AssetsCount)
                {
                    req.OnCompleted();
                }
            };

            return req;
        }

        public static AssetsTableRequest<T> LoadAssetsTableAsync<T>(List<string> paths) where T : Object
        {
            var req = new AssetsTableRequest<T>(paths.Count);
            var locations = Addressables.LoadResourceLocationsAsync(paths, Addressables.MergeMode.Union, typeof(T));
            locations.Completed += (op) =>
            {
                foreach (IResourceLocation location in locations.Result)
                {
                    AsyncOperationHandle<T> handle = Addressables.LoadAssetAsync<T>(location);
                    handle.Completed += obj => { req.OnAssetCompleted(location.PrimaryKey, obj.Result); };
                }
            };
            return req;
        }

        public static void Unload(Object obj) 
        {
#if UNITY_EDITOR
            if (IsDebug)
            {
                //return AssetDatabase.LoadMainAssetAtPath("Assets/" + path);
            }
            else
            {
#endif

                // 删除资源

#if UNITY_EDITOR
            }
#endif
        }

        private static bool TryGetLoadedAssetBundle(string name, out AssetBundle assetbundle)
        {
            foreach (var item in AssetBundle.GetAllLoadedAssetBundles())
            {
                if (item.name == name)
                {
                    assetbundle = item;
                    return true;
                }
            }
            assetbundle = null;
            return false;
        }

        private static void LoadAssetBundleAsync(string name, UnityAction<AssetBundle> callback)
        {
            if (TryGetLoadedAssetBundle(name, out AssetBundle assetbundle))
            {
                callback.Invoke(assetbundle);
                return;
            }
            else
            {
                AssetBundleCreateRequest req = AssetBundle.LoadFromFileAsync(AssetManager.Instance.Configuration.AssetSettingConfiguration.GetAssetBundleNameFromAssetName("Assets/" + name));
                req.completed += (e) => { callback.Invoke((e as AssetBundleCreateRequest).assetBundle); };
            }
        }

        #region Resource加载

        private static Object LoadResource(string resourcepath)
        {
            return Resources.Load(Path.GetFileNameWithoutExtension(resourcepath.Substring(ResourcePathTags.Length)));
        }

        private static T LoadResource<T>(string resourcepath) where T : Object
        {
            return Resources.Load<T>(Path.GetFileNameWithoutExtension(resourcepath.Substring(ResourcePathTags.Length)));
        }

        private static AssetRequest LoadResourceAsync(string resourcepath)
        {
            var req = new AssetRequest();
            Resources.LoadAsync(Path.GetFileNameWithoutExtension(resourcepath.Substring(ResourcePathTags.Length))).completed += (e) => {
                req.Asset = (e as ResourceRequest).asset;
                req.OnCompleted();
            };
            return req;
        }

        private static AssetRequest<T> LoadResourceAsync<T>(string resourcepath) where T : Object
        {
            var req = new AssetRequest<T>();
            Resources.LoadAsync(Path.GetFileNameWithoutExtension(resourcepath.Substring(ResourcePathTags.Length))).completed += (e) => {
                req.Asset = (e as ResourceRequest).asset as T;
                req.OnCompleted();
            };
            return req;
        }

        private static AssetsRequest LoadResourcesAsync(List<string> resourcepaths)
        {
            var req = new AssetsRequest();
            req.AssetsCount = resourcepaths.Count;
            foreach (var item in resourcepaths)
            {
                Resources.LoadAsync(Path.GetFileNameWithoutExtension(item.Substring(ResourcePathTags.Length))).completed += (e) => {
                    req.OnAssetCompleted((e as ResourceRequest).asset);
                    if(req.Assets.Count >= req.AssetsCount)
                    {
                        req.OnCompleted();
                    }
                };
            }
            return req;
        }

        private static AssetsRequest<T> LoadResourcesAsync<T>(List<string> resourcepaths) where T : Object
        {
            var req = new AssetsRequest<T>
            {
                AssetsCount = resourcepaths.Count
            };
            foreach (var item in resourcepaths)
            {
                Resources.LoadAsync(Path.GetFileNameWithoutExtension(item.Substring(ResourcePathTags.Length))).completed += (e) => {
                    req.OnAssetCompleted((e as ResourceRequest).asset as T);
                    if (req.Assets.Count >= req.AssetsCount)
                    {
                        req.OnCompleted();
                    }
                };
            }
            return req;
        }


        #endregion

        #region Asset加载
        private static Object LoadAsset(string assetpath)
        {
#if UNITY_EDITOR
            if ( IsDebug )
            {
                var req = Addressables.LoadAssetAsync<Object>(assetpath);
                return req.WaitForCompletion();
                //return AssetDatabase.LoadMainAssetAtPath(assetpath);
            }
            else
            {
#endif
                var req = Addressables.LoadAssetAsync<Object>(assetpath);
                return req.WaitForCompletion();
#if UNITY_EDITOR
            }
#endif
        }

        private static T LoadAsset<T>(string assetpath) where T : Object
        {
#if UNITY_EDITOR
            if (IsDebug)
            {
                if(typeof(T).IsSubclassOf(typeof(Component)))
                {
                    var req = Addressables.LoadAssetAsync<GameObject>(assetpath);
                    var go = req.WaitForCompletion();
                    var com = go.GetComponent<T>();
                    if (null == com) com = go.AddComponent(typeof(T)) as T;
                    return com;
                }
                else
                {
                    var req = Addressables.LoadAssetAsync<T>(assetpath);
                    return req.WaitForCompletion();
                }
            }
            else
            {
#endif
                var req = Addressables.LoadAssetAsync<T>(assetpath);
                return req.WaitForCompletion();
#if UNITY_EDITOR
            }
#endif
        }

        private static AssetRequest LoadAssetAsync(string assetpath)
        {
#if UNITY_EDITOR
            if (IsDebug)
            {
                return AssetManager.Instance.LoadAssetAsync(assetpath);
            }
            else
            {
#endif
                var req = new AssetRequest();
                Addressables.LoadAssetAsync<Object>(assetpath).Completed += (op) => {
                    req.Asset = op.Result;
                    req.OnCompleted();
                };
                return req;
#if UNITY_EDITOR
            }
#endif
        }

        private static AssetRequest<T> LoadAssetAsync<T>(string assetpath) where T : Object
        {
#if UNITY_EDITOR
            if (IsDebug)
            {
                return AssetManager.Instance.LoadAssetAsync<T>(assetpath);
            }
            else
            {
#endif
                var req = new AssetRequest<T>();
                Addressables.LoadAssetAsync<T>(assetpath).Completed += (op) => {
                    req.Asset = op.Result;
                    req.OnCompleted();
                };
                return req;
#if UNITY_EDITOR
            }
#endif
        }

        private static AssetsRequest LoadAssetsAsync(List<string> assetpaths)
        {
            var req = new AssetsRequest
            {
                AssetsCount = assetpaths.Count
            };
            Addressables.LoadAssetsAsync<Object>(assetpaths, (obj) => {
                req.OnAssetCompleted(obj);
                if (req.Assets.Count >= req.AssetsCount)
                {
                    req.OnCompleted();
                }
            });
            return req;
//#if UNITY_EDITOR
//            if (IsDebug)
//            {
//                return AssetManager.Instance.LoadAssetsAsync(assetpaths);
//            }
//            else
//            {
//#endif          
//                var req = new AssetsRequest
//                {
//                    AssetsCount = assetpaths.Count
//                };
//                Addressables.LoadAssetsAsync<Object>(assetpaths, (obj) =>{
//                    req.OnAssetCompleted(obj);
//                    if (req.Assets.Count >= req.AssetsCount)
//                    {
//                        req.OnCompleted();
//                    }
//                });
//                return req;
//#if UNITY_EDITOR
//            }
//#endif
        }

        private static AssetsRequest<T> LoadAssetsAsync<T>(List<string> assetpaths) where T : Object
        {
            var req = new AssetsRequest<T>
            {
                AssetsCount = assetpaths.Count
            };
            var req1 = Addressables.LoadAssetsAsync<T>(assetpaths, (obj) => {
                req.OnAssetCompleted(obj);
                if (req.Assets.Count >= req.AssetsCount)
                {
                    req.OnCompleted();
                }
            });
            return req;
//#if UNITY_EDITOR
//            if (IsDebug)
//            {
//                return AssetManager.Instance.LoadAssetsAsync<T>(assetpaths);
//            }
//            else
//            {
//#endif
//                var req = new AssetsRequest<T>
//                {
//                    AssetsCount = assetpaths.Count
//                };
//                var req1 = Addressables.LoadAssetsAsync<T>(assetpaths, (obj) =>{
//                    req.OnAssetCompleted(obj);
//                    if (req.Assets.Count >= req.AssetsCount)
//                    {
//                        req.OnCompleted();
//                    }
//                });
//                return req;
//#if UNITY_EDITOR
//            }
//#endif
        }

        #endregion
    }
}
